/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

****/
package org.activequant.core.domainmodel.data;

import java.io.Serializable;
import java.util.List;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.SeriesSpecification;
import org.activequant.core.types.TimeStamp;
import org.activequant.core.types.Tuple;
import org.activequant.core.util.TimeSeriesUtils;
import org.activequant.util.exceptions.ValueNotFoundException;
import org.activequant.util.tools.ArrayUtils;



/**
 * TimeSeries class. Based on TimeSeries class of ccapi2<br/>
 * A TimeSeries(ArrayList subclass) whose elements are Candle objects.
 * Holds the following inherited/associated variables:
 * <ul>
 * <li>id(Long)</li>
 * <li>seriesSpecification(SeriesSpecification)</li>
 * <li>inherited list of elements (List&lt;Candle&gt;)</li>
 * </ul>
 * <br>
 * <b>History:</b><br>
 *  - [03.05.2007] File created (Erik Nijkamp)<br>
 *  - [23.06.2007] Moved to new domain model (Erik Nijkamp)<br>
 *  - [12.07.2007] Finalized clone method (Ulrich Staudinger)<br>
 *  - [05.08.2007] fixed clone() (Erik Nijkamp)<br>
 *  - [31.08.2007] added getClosesAsTuples (Ulrich Staudinger)<br>
 *  - [31.08.2007] added Tuple constructor (Ulrich Staudinger)<br>
 *  - [28.09.2007] removed baseclass, removed instrumentspec (Erik Nijkamp)<br>
 *  - [28.09.2007] moved to new domain model (Erik Nijkamp)<br>
 *  - [06.11.2007] moved generic functionality to TimeSeries<T> (Erik Nijkamp)<br>
 *  - [17.01.2008] Added getRelativeReturns functionality. (Ulrich Staudinger)<br>
 *  
 *  @author Ulrich Staudinger
 *  @author Erik Nijkamp
 */
public class CandleSeries extends TimeSeries<Candle> implements Serializable {

	private static final long serialVersionUID = 7075375398315155768L;
	
	/**
	 * an empty CandleSeries constructor.
	 *
	 */
	public CandleSeries() {
		super();
	}
	
	/**
	 * Constructs a CandleSeries(ArrayList and TimeSeries subclass) using the given candles(Candle...) to set its Candle elements.<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n)
	 * @param specs
	 * @param candles
	 *
	 */
	public CandleSeries(Candle... candles) {
		setCandles(candles);
	}
	/**
	 * Constructs a CandleSeries(ArrayList and TimeSeries subclass) using the given spec(SeriesSpecification) and candles(Candle...) to set its associated 
	 * seriesSpecification(SeriesSpecification) and its Candle elements.<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n)
	 * @param spec
	 * @param candles
	 */
	public CandleSeries(SeriesSpecification spec, Candle... candles) {
		this(candles);
		setSeriesSpecification(spec);
	}
	
    /**
     * Constructs a CandleSeries(ArrayList and TimeSeries subclass) using the given spec(InstrumentSpecification) and tuples(List&lt;Tuple&lt;TimeStamp,Double&gt;&gt;) to initialize add set its Candle elements.<br/>
     * The given spec(InstrumentSpecification) is used to set the instrumentSpecification(InstrumentSpecification) of each Candle.<br/>
     * The TimeStamp and Double of each given Tuple are used to set the timeStamp(TimeStamp) and closePrice(double) of each Candle.<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n).   
     * constructor for building candle list from tuples. 
     * @param list
     */
    public CandleSeries(InstrumentSpecification spec, List<Tuple<TimeStamp, Double>> tuples) {
    	for(Tuple<TimeStamp, Double> t : tuples){
    		Candle c = new Candle();
    		c.setInstrumentSpecification(spec);
    		c.setTimeStamp(t.getObject1());
    		c.setClosePrice(t.getObject2());
    		this.add(c);
    	}
    	assert(TimeSeriesUtils.isOrderValid(this));
    }

    /**
     * Constructs a CandleSeries(ArrayList and TimeSeries subclass) using the given candles(List&lt;Candle&gt;) to set its Candle elements.<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n).    
     * @param list
     */
    public CandleSeries(List<Candle> candles) {
    	super(candles);
    	assert(TimeSeriesUtils.isOrderValid(this));
    }
    /**
     * Constructs a CandleSeries(ArrayList and TimeSeries subclass).
     * The given dates(TimeStamp[]), opens(double[]), highs(double[]), lows(double[]) and closes(double[]) are used to initialize add set its Candle elements.<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n).    
     * @param dates
     * @param opens
     * @param highs
     * @param lows
     * @param closes
     */
    public CandleSeries(TimeStamp[] dates, 
    		double[] opens, 
    		double[] highs,
			double[] lows, 
			double[] closes) {
		// members
    	for(int i = 0; i < dates.length; i++) {
    		add(new Candle(dates[i], opens[i], highs[i], lows[i], closes[i]));
    	}
    	assert(TimeSeriesUtils.isOrderValid(this));
    }
    /**
     * Constructs a CandleSeries(ArrayList and TimeSeries subclass) using the given spec(SeriesSpecification) to set its associated seriesSpecification(SeriesSpecification).<br/>
     * The given dates(TimeStamp[]), opens(double[]), highs(double[]), lows(double[]) and closes(double[]) are used to initialize add set its Candle elements.<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n).
     * @param spec
     * @param dates
     * @param opens
     * @param highs
     * @param lows
     * @param closes
     */
    public CandleSeries(SeriesSpecification spec,
    		TimeStamp[] dates, 
    		double[] opens, 
    		double[] highs,
			double[] lows, 
			double[] closes) {
    	super(spec);
		// members
    	for(int i = 0; i < dates.length; i++) {
    		add(new Candle(getInstrumentSpecification(), dates[i], opens[i], highs[i], lows[i], closes[i]));
    	}
    	assert(TimeSeriesUtils.isOrderValid(this));
    }
    /**
     * applies a given series specification to the series itself and all elements<br/>
	 * <strong>1.</strong> sets the associated seriesSpecification(SeriesSpecification) with the given seriesSpecification(SeriesSpecification)<br/>
	 * <strong>2.</strong> sets the instrumentSpecification(InstrumentSpecification) and timeFrame(TimeFrame) of each of the Candle elements of this CandleSeries(ArrayList and TimeSeries subclass)
	 * with the instrumentSpecification(InstrumentSpecification) and timeFrame(TimeFrame) of the given seriesSpecification(SeriesSpecification)
     */
    @Override
	public void applySeriesSpecification(SeriesSpecification seriesSpecification) {
		assert(seriesSpecification!=null);
		this.seriesSpecification = seriesSpecification;
		for(Candle object : this) {
			object.setInstrumentSpecification(seriesSpecification.getInstrumentSpecification());
			object.setTimeFrame(seriesSpecification.getTimeFrame());
		}
	}
    /**
     * Sets the elements of this CandleSeries(ArrayList and TimeSeries subclass) with the given candles(Candle...).<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n)
     * @param candles
     */
    public void setCandles(Candle... candles) {
    	setCandles(ArrayUtils.asList(candles));
    }
    /**
     * Sets the elements of this CandleSeries(ArrayList and TimeSeries subclass) with the given candles(List&lt;Candle&gt;).<br/>
     * Their timeStamp(TimeStamp)'s must be ordered from newest(at index=0) to oldest(at index=n)
     * @param candles
     */
    public void setCandles(List<Candle> candles) {
    	assert(TimeSeriesUtils.isOrderValid(this));
    	clear();
    	addAll(candles);
    }
    /**
     * returns a Candle[] array containing the elements of this CandleSeries(ArrayList and TimeSeries subclass)
     * @return
     */
    public Candle[] getCandles() {
    	return ArrayUtils.asArray(this, Candle.class);
    }

    /**
	 * returns the Candle whose lowPrice(double) is the lowest amongst the elements of this CandleSeries(ArrayList and TimeSeries subclass)
	 * @return the low candle of this series.
	 */
    public Candle getLow() {
        Candle min = get(0);
        for (Candle c : this) {
            if (c.getLowPrice() < min.getLowPrice()) {
            	min = c;
            }
        }
        return min;
    }

    /**
     * returns the Candle whose highPrice(double) is the highest amongst the elements of this CandleSeries(ArrayList and TimeSeries subclass)
     * @return the high candle of this series.
     */
    public Candle getHigh() {
    	Candle max = get(0);
        for (Candle c : this) {
            if (c.getHighPrice() > max.getHighPrice()) {
            	max = c;
            }
        }
        return max;
    }
    /**
     * sets the openPrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass) with a value from the given doubles(double[])
     * @param doubles
     */
    public void setOpens(double[] doubles) {
    	assert(doubles.length == size()) : "length invalid";
    	for(int i = 0; i < size(); i++) {
    		get(i).setOpenPrice(doubles[i]);
    	}
    }
    /**
     * sets the highPrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass) with a value from the given doubles(double[])
     * @param doubles
     */
    public void setHighs(double[] doubles) {
    	assert(doubles.length == size()) : "length invalid";
    	for(int i = 0; i < size(); i++) {
    		get(i).setHighPrice(doubles[i]);
    	}
    }
    /**
     * sets the lowPrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass) with a value from the given doubles(double[])
     * @param doubles
     */
    public void setLows(double[] doubles) {
    	assert(doubles.length == size()) : "length invalid";
    	for(int i = 0; i < size(); i++) {
    		get(i).setLowPrice(doubles[i]);
    	}
    }
    /**
     * sets the closePrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass) with a value from the given doubles(double[])
     * @param doubles
     */
    public void setCloses(double[] doubles) {
    	assert(doubles.length == size()) : "length invalid";
    	for(int i = 0; i < size(); i++) {
    		get(i).setClosePrice(doubles[i]);
    	}
    }
    
    /**
     * returns a double[] array containing the closePrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass)<br/>
     * returns all close values in a double array. As a candleseries gets longer, 
     * this method takes more time, as it does not implement any sort of caching.
     * It simply generates a double array, copies all close values into it and
     * returns the array. (Reminder: could be improved.)
     * @return
     */
    public double[] getCloses() {
        double[] doubles = new double[size()];
        int idx = 0;
        for (Candle c : this) {
        	doubles[idx++] = c.getClosePrice();
        }
        return doubles;
    }
    /**
     * returns a double[] array containing the openPrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass)
     * @return
     */
    public double[] getOpens() {
        double[] doubles = new double[size()];
        int idx = 0;
        for (Candle c : this) {
        	doubles[idx++] = c.getOpenPrice();
        }
        return doubles;
    }
    /**
     * returns a double[] array containing the highPrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass)
     * @return
     */
    public double[] getHighs() {
        double[] doubles = new double[size()];
        int idx = 0;
        for (Candle c : this) {
        	doubles[idx++] = c.getHighPrice();
        }
        return doubles;
    }
    /**
     * returns a double[] array containing the lowPrice(double)'s of each Candle element of this CandleSeries(ArrayList and TimeSeries subclass)
     * @return
     */
    public double[] getLows() {
        double[] doubles = new double[size()];
        int idx = 0;
        for (Candle c : this) {
        	doubles[idx++] = c.getLowPrice();
        }
        return doubles;
    }
    /**
     * returns a double[] array containing the relative returns of the Candle elements of this CandleSeries(ArrayList and TimeSeries subclass)<br/>
     * This double[] array has one less element than the size of the CandleSeries and each element is calculated in the following way:<br/>
     * <strong>ret[i]=candle[i].closePrice/candle[i+1].closePrice</strong>
     * @return
     */
    public double[] getRelativeReturns(){
    	double[] ret = new double[this.size()-1];
    	for(int i=this.size()-2;i>0;i--){
    		ret[i] = this.get(i).getClosePrice() / this.get(i+1).getClosePrice();
    	}
    	return ret; 
    }
    
	public CandleSeries clone() {
		Candle[] clonedCandles = new Candle[size()];
		Candle[] candles = toArray(new Candle[]{});
		for(int i = 0; i < candles.length; i++) {
			clonedCandles[i] = candles[i].clone();
		}
		CandleSeries clone = new CandleSeries(seriesSpecification, clonedCandles);
		return clone; 
	}
	/**
	 * returns a CandleSeries containing the Candle elements whose TimeStamps are within(inclusive) the given start-end(TimeStamp) interval
	 */
    public CandleSeries getTimeFrame(TimeStamp start, TimeStamp end) {
    	return (CandleSeries) super.getTimeFrame(start, end);
    }
	/**
	 * returns a CandleSeries containing the Candle elements within(inclusive) the given start-end(int) index interval
	 */
	public CandleSeries subList(int start, int end) throws ValueNotFoundException {
		return (CandleSeries) super.subList(start, end);
	}
	/**
	 * returns a CandleSeries containing the Candle elements whose TimeStamps are within(inclusive) the given start-end(TimeStamp) interval
	 */
	public CandleSeries subList(TimeStamp start, TimeStamp end) throws ValueNotFoundException {
		return (CandleSeries) super.subList(start, end);
	}
}